using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Net;

using FarseerGames.FarseerPhysics.Factories;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Dynamics.Springs;
using FarseerGames.FarseerPhysics.Collisions;

namespace GrapplingHookGameData
{
    public class SpringRectangleRope
    {

        #region Fields
        Texture2D rectangleTexture;
        Body[] rectangleBody;
        Geom[] rectangleGeom;
        LinearSpring[] linearSpring;

        int rectangleWidth = 20;
        int rectangleHeight = 20;
        float rectangleMass = 0.1f;
        int rectangleCount = 20;

        float springLength = 25;
        float restLength = 25;
        float springConstant = 1;
        float dampningConstant = 1;

        int collisionGroup = 0;

        RopeStates ropeState = RopeStates.NONE;
        enum RopeStates
        {
            NONE, EXTENDING, CONNECTED
        }

        LinearSpring ropePlayerAttach;
        FixedLinearSpring ropeWorldAttach;
        private int segmentsInUse;

        protected IGameDataCallbacks gameCallback;
        PhysicsObject anchor;
        ContentManager content;
        PhysicsSimulator physicsSimulator;
        Player player;
        static LineBrush lineBrush = new LineBrush(1, Color.Yellow);

        #endregion

        #region Properties
        public Body FirstBody
        {
            get { return rectangleBody[0]; }
        }

        public Body LastBody
        {
            get { return rectangleBody[segmentsInUse - 1]; }
        }

        public int RectangleCount
        {
            get { return rectangleCount; }
            set { rectangleCount = value; }
        }

        public int RectangleWidth
        {
            get { return rectangleWidth; }
            set { rectangleWidth = value; }
        }

        public int RectangleHeight
        {
            get { return rectangleHeight; }
            set { rectangleHeight = value; }
        }

        public float RectangleMass
        {
            get { return rectangleMass; }
            set { rectangleMass = value; }
        }

        public float SpringLength
        {
            get { return springLength; }
            set { springLength = value; }
        }

        public float RestLength
        {
            get { return restLength; }
            set { restLength = value; }
        }

        public float SpringConstant
        {
            get { return springConstant; }
            set { springConstant = value; }
        }

        public float DampningConstant
        {
            get { return dampningConstant; }
            set { dampningConstant = value; }
        }

        public int CollisionGroup
        {
            get { return collisionGroup; }
            set { collisionGroup = value; }
        }
        #endregion

        #region Initialization
        public SpringRectangleRope(IGameDataCallbacks gameCallback, Player player)
        {
            this.gameCallback = gameCallback;

            RectangleMass = 0.1f;
            RectangleWidth = 10;
            RectangleHeight = 10;
            SpringLength = 10;
            RestLength = 7;
            CollisionGroup = 2;
            SpringConstant = 200;
            //rope.DampningConstant = 4;

            anchor = new PhysicsObject(gameCallback, Vector2.Zero, "anchor");

            RectangleCount = (int)Math.Round( Constants.ROPE_MAX_LENGTH / Constants.ROPE_JOINT_DISTANCE);
            this.player = player;
        }


        public void LoadContent(GraphicsDevice graphicsDevice, PhysicsSimulator physicsSimulator, ContentManager content) {
            this.content = content;
            this.physicsSimulator = physicsSimulator;
            rectangleTexture = DrawingHelper.CreateRectangleTexture(graphicsDevice, rectangleWidth, rectangleHeight, Color.White, Color.Yellow);
            lineBrush.Load(graphicsDevice);

            //create bodies representing rope
            rectangleBody = new Body[rectangleCount];
            rectangleBody[0] = BodyFactory.Instance.CreateRectangleBody(physicsSimulator,rectangleWidth, rectangleHeight, rectangleMass);
            rectangleBody[0].Position = Vector2.Zero;
            rectangleBody[0].Enabled = false;
            rectangleBody[0].MomentOfInertia = float.PositiveInfinity;  //stabilize simulation by not allowing rope segments to rotate
            rectangleBody[0].Tag = "rope segmetnt 0";    //debug
            for (int i = 1; i < rectangleBody.Length; i++)
            {
                rectangleBody[i] = BodyFactory.Instance.CreateBody(physicsSimulator,rectangleBody[0]);
                rectangleBody[i].Position = rectangleBody[i - 1].Position + new Vector2(0, springLength);       //rope just hanging down
                rectangleBody[i].Enabled = false;
                rectangleBody[i].MomentOfInertia = float.PositiveInfinity;  //stabilize simulation by not allowing rope segments to rotate
                rectangleBody[i].Tag = "rope segmetnt " + i;    //debug
            }

            //create geoms for each rope segment
            rectangleGeom = new Geom[rectangleCount];
            rectangleGeom[0] = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator,rectangleBody[0], rectangleWidth, rectangleHeight);
            rectangleGeom[0].CollisionGroup = collisionGroup;
            rectangleGeom[0].Tag = this;
            for (int j = 1; j < rectangleGeom.Length; j++) {
                rectangleGeom[j] = GeomFactory.Instance.CreateGeom(physicsSimulator,rectangleBody[j], rectangleGeom[0]);
                rectangleGeom[j].Tag = this;
            }

            //create springs connecting rope segments
            linearSpring = new LinearSpring[rectangleCount - 1];
            for (int k = 0; k < linearSpring.Length; k++)
            {
                linearSpring[k] = ControllerFactory.Instance.CreateLinearSpring(physicsSimulator,rectangleBody[k], Vector2.Zero, rectangleBody[k + 1], Vector2.Zero, springConstant, dampningConstant);
                linearSpring[k].RestLength = restLength;
                linearSpring[k].Enabled = false;
                linearSpring[k].Breakpoint = restLength * 10;
                linearSpring[k].Broke += new EventHandler<EventArgs>(RopeBroke);
            }
            
            //anchor
            anchor.CreateBody = true;
            anchor.CreateGeom = true;
            anchor.LoadContent(content, physicsSimulator);
            anchor.Body.Mass = 0.3f;
            anchor.Geom.CollisionGroup = 1;
            anchor.Geom.OnCollision += new Geom.CollisionEventHandler(onRopeAnchorCollision);
            anchor.Body.Enabled = false;

        }
        #endregion

        public bool onRopeAnchorCollision(Geom geometry1, Geom geometry2, ContactList contactList)
        {
            if (geometry2.Body.IsStatic)
            {
                anchor.Body.Enabled = false; //stop anchor
                ConnectRope();
            }
            return false;
        }

                /// <summary>
        /// Update physics forces on player depending on next requested action.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime, bool isHost)
        {
            if (ropeState == RopeStates.EXTENDING)
            {
                anchor.Update(gameTime, isHost);
                //check for max extension of rope
                if (((anchor.Body.Position - player.Body.Position).Length() > Constants.ROPE_MAX_LENGTH))
                {
                    DisconnectRope();
                }
            }

        }

        private void RopeBroke(object source, EventArgs e)
        {
            DisconnectRope();
        }

        /// <summary>
        /// 
        /// </summary>
        private void ConnectRope()
        {

            Vector2 ropeDirection = player.Body.Position - anchor.Position -new Vector2( 0, player.Height/2);
            float ropeLength = ropeDirection.Length();
            segmentsInUse = (int)MathHelper.Clamp((float)Math.Round(ropeLength / Constants.ROPE_JOINT_DISTANCE), 1, Constants.ROPE_MAX_LENGTH / Constants.ROPE_JOINT_DISTANCE);
            ropeDirection.Normalize();

           //enable bodies
            for (int i = 0; i < segmentsInUse; i++)
            {
                rectangleBody[i].Position = anchor.Position + ropeDirection*Constants.ROPE_JOINT_DISTANCE*i;
                rectangleBody[i].Enabled = true;
                rectangleBody[i].Tag = "rope part " + i;    //debug
            }
            //enable springs
            for (int i = 0; i < segmentsInUse - 1; i++)
            {
                //linearSpring[i].SpringError = 0;
                linearSpring[i].Enabled = true;
            }

            //connect rope ends
            ropeWorldAttach = ControllerFactory.Instance.CreateFixedLinearSpring(physicsSimulator, FirstBody, Vector2.Zero, anchor.Position, 200, 4);
            ropeWorldAttach.RestLength = restLength;
            ropeWorldAttach.Breakpoint = restLength * 10;
            ropeWorldAttach.Broke += new EventHandler<EventArgs>(RopeBroke);

            ropePlayerAttach = ControllerFactory.Instance.CreateLinearSpring(physicsSimulator, LastBody, Vector2.Zero, player.Body, new Vector2(0, -player.Height / 2f), 200, 4);
            ropePlayerAttach.RestLength = restLength;
            ropePlayerAttach.Breakpoint = restLength * 10;
            ropePlayerAttach.Broke += new EventHandler<EventArgs>(RopeBroke);

            ropeState = RopeStates.CONNECTED;
            gameCallback.RopeConnected();

        }


        public void FireAnchor()
        {
            DisconnectRope();
            anchor.Body.ResetDynamics();
            anchor.Position = player.Body.Position + new Vector2(0, -(player.Height+anchor.Height) / 2f);
            anchor.Body.Enabled = true;
            anchor.Body.ApplyImpulse(new Vector2(80 * player.playerDirection, -80));
            ropeState = RopeStates.EXTENDING;
        }

        public void DisconnectRope()
        {
            ropeState = RopeStates.NONE;
            anchor.Body.Enabled = false;
            foreach (Body b in rectangleBody)
            {
                b.Enabled = false;
                b.ResetDynamics();
            }
            foreach (LinearSpring s in linearSpring)
            {
                s.Enabled = false;
            }
            if (ropePlayerAttach != null)
            {
                ropePlayerAttach.Enabled = false;
                physicsSimulator.Remove(ropePlayerAttach);
            }
            if (ropeWorldAttach != null)
            {
                ropeWorldAttach.Enabled = false;
                physicsSimulator.Remove(ropeWorldAttach);
            }
        }


        #region Draw
        public void Draw(SpriteBatch spriteBatch)
        {
            switch( ropeState)
            {
                case RopeStates.EXTENDING:
                    anchor.Draw(spriteBatch);
                    lineBrush.Draw(spriteBatch, player.Position - new Vector2(0, (player.Height-anchor.Height)/ 2f), anchor.Position);
                    break;
                case RopeStates.CONNECTED:

                    anchor.Draw(spriteBatch);
                    Vector2 segmentStartPos = anchor.Position;
                    Vector2 segmentEndPos;
                    for (int i = 0; i < segmentsInUse; i++)
                    {
                        segmentEndPos = rectangleGeom[i].Position;
                        lineBrush.Draw(spriteBatch, segmentStartPos, segmentEndPos);
                        //spriteBatch.Draw(rectangleTexture, rectangleGeom[i].Position, null, Color.White, rectangleGeom[i].Rotation, new Vector2(rectangleTexture.Width / 2, rectangleTexture.Height / 2), 1, SpriteEffects.None, 0);
                        segmentStartPos = segmentEndPos;
                    }
                    break;
            }
        }
        #endregion

        #region Networking
        /// <summary>
        /// Writes the data that is needed by clients to draw this rope
        /// </summary>
        /// <param name="packetWriter"></param>
        public void WriteDataToClients(PacketWriter packetWriter)
        {
            packetWriter.Write((int)ropeState);
            packetWriter.Write(anchor.Position);
            if (ropeState == RopeStates.CONNECTED)
            {
                packetWriter.Write(segmentsInUse);
                for (int i = 0; i < segmentsInUse; i++)
                {
                    packetWriter.Write(rectangleBody[i].Position);
                }
            }
        }

        /// <summary>
        /// Read the data that is needed by clients to draw this rope
        /// </summary>
        /// <param name="packetWriter"></param>
        public void ReadDataFromHost(PacketReader packetReader)
        {
            ropeState = (RopeStates)packetReader.ReadInt32();
            anchor.Position = (Vector2)packetReader.ReadVector2();
            if (ropeState == RopeStates.CONNECTED)
            {
                segmentsInUse = packetReader.ReadInt32();
                for (int i = 0; i < segmentsInUse; i++)
                {
                    rectangleBody[i].Position = (Vector2)packetReader.ReadVector2();
                }
            }
        }
        #endregion
    }
}
